<?php

/**
 * Sample File 3, phpDocumentor Quickstart
 * 
 * This file demonstrates the use of the @name tag
 * @author Albert Mavrin <albert.mavrin@gmail.com>
 * @version 1.0
 * @package IDSys
 */

session_start();

require_once("cfg/cfg.php");
require_once("lib/mysql.class.php");
require_once("lib/timer.class.php");
require_once("lib/logger.class.php");
require_once("lib/localestrings.class.php");


/**
 * Класс для обслуживания идентификаторов пользователя 
 * 
 * <p>Идентификация и регистрации пользователя,
 * а также обслуживания его сессий</p>
 * @package IDSys
 */
class UID
{
	public $info = "";
	private $buf = null;
	private $err = false;
	
	private $loc = null;
	
	private $user_fields = "user_id, phone, email, password, dt, email_state, state";
	private $nick_fields = "nick, user_id, dt, state";
/**
 * Конструктор
 *
 * <ul>
 * <li>Пытается получить user_id из сессии сервере - $_SESSION['user_id']</li>
 * <li>Инициализирует соединение с БД, устанавливает пути логирования</li>
 * </ul>
 */
	public function __construct()
	{
		$this->init();
	}
	
/**
 * Настройка библиотек доступа к БД и логирования
 *
 * <p>Установка параметров для внешних библиотек</p>
 */
	private function init()
	{
		MySQL::$url = DBURL;
		$this->db = MySQL::getInstance();
		Logger::$log_file = USR_LOG_FILE;
		$this->loc = new LocaleStrings('ru', 'uid');
	}
	
/**
 * Идентификация пользователя
 * 
 * <p>Сначала проверяет POST-параметра "phone", затем - "passw", если он существует,
 * тогда вызывается проверка телефона и пароля, если нет - проверка через звонок</p>
 *
 * <p>Если телефон не пришел параметром, тогда проверяется существование
 * ID пользователя в сессии, а если его нет, тогда ищется ID сессии,
 * сначала в запросе (GET), а потом - в куках </p>
 *
 * @return boolean true - если идентификация проведена успешно, false - неуспех
 */
	public function identify()
	{
		$res = false;
		
		$this->info = "identify";
			
		// проверка получения внешних идентификаторов
		if(isset($_POST['phone']))
		{
			// проверяем телефон/пароль
			if(isset($_POST['passw']))
			{
				$res = $this->login();
				$this->info = "login";
			}
			// пароль не пришел, значит ждем звонка
			else
			{
				$res = $this->wait_call();
				$this->info = "wait_call";
			}
		}
		// проверка сессии
		else
		{
			// если сессия содержит ID пользователя
			if(!empty($_SESSION['uid']))
			{
				$this->info = "UID in session";
				$res = true;
			}
			
			// если запрос содержит ID сессии
			elseif(!empty($_GET['sid']))
			{
				$uid = $this->sid2uid($_GET['sid']);
				if(!empty($uid))
				{
					$_SESSION['uid'] = $uid;
					$this->info = "SID in GET";
					$res = true;
				}
				else
				{
					$this->info .= $this->loc->get("dberr", "sid-wo-uid", array("sid"=>$uid));
					$res = false;
				}
			}
			
			// если куки содержат ID сессии
			elseif(!empty($_COOKIE['sid']))
			{
				$uid = $this->sid2uid($_COOKIE['sid']);
				if(!empty($uid))
				{
					$_SESSION['uid'] = $uid;
					$this->info = "SID in cookie";
					$res = true;
				}
				else
				{
					$this->info .= $this->loc->get("dberr", "sid-wo-uid", array("sid"=>$uid));
					$res = false;
				}
			}
		}
		$this->log2db('SIGNIN', $this->info);
		return $res;
	}
	
	private function wait_call()
	{
		$this->info = "wait call";
		
		$phone = $_POST['phone'];
		$url = PATH2CALL.'?act=identify&timeout='.CALLWAIT_TIMEOUT_SEC.'&phone='.$phone;
		
		$ch = curl_init();  

		curl_setopt($ch, CURLOPT_URL, $url); // set url to post to  
		curl_setopt($ch, CURLOPT_FAILONERROR, 1);  
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);// allow redirects  
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // return into a variable  
		curl_setopt($ch, CURLOPT_TIMEOUT, CALLWAIT_TIMEOUT_SEC + 120); // times out after 4s  
		curl_setopt($ch, CURLOPT_POST, 0); // set POST method  
		$result = curl_exec($ch); // run the whole process  
		curl_close($ch);   

		$json = json_decode($result, true);
		return $json['CODE'] == '200';
	}
	
	private function login()
	{
		$this->info = "login";
		
		if(empty($_POST['phone']) || empty($_POST['passw'])) return false;
		
		$phone = $_POST['phone'];
		$passw = $_POST['passw'];
		$hash = md5(md5($passw).SALT);
		
		$sql = "SELECT ".$this->user_fields." FROM users WHERE phone = '$phone'";
		$this->show2($sql);
		
		$res = $this->db->query($sql);
		if($this->db->num_rows($res) === 0) return null;
		$row = $this->db->fetch_assoc($res);
		$this->log2("ROW: ".print_r($row, true));
		if($row["password"] == $hash)
		{
			$_SESSION['uid'] = $row["user_id"];
			$_SESSION["user"] = $row;
			
			$this->refresh_session();
			
			return true;
		}
		else
			return false;
	}
	
/**
 * Регистрация пользователя
 * 
 * <p>Попытка повторного использования телефона вызывает исключение.</p>
 * @param	string	$phone Номер телефона пользователя
 * @param	string	$nick псевдоним пользователя
 * @param	string	$password пароль
 * @return	int	если все в порядке, то возвращается ID пользователя 
 */
	public function register($phone, $nick, $password, $email = null)
	{
		$this->info = "";
		
		$uid = $this->is_phone_exists($phone);
		if($uid)
		{
			trigger_error($this->loc->get("dberr", "phone-exists", array("phone"=>$phone)), E_USER_ERROR);
		}
		else
		{
			if($this->is_nick_exists($nick))
			{
				trigger_error($this->loc->get("dberr", "nick-exists", array("nick"=>$nick)), E_USER_ERROR);
			}
			else
			{
				$_SESSION['uid'] = $this->create_user_row($phone, $password, $email);
				$this->create_nick_row($nick, $_SESSION['uid']);
			}
		}
		$this->refresh_session();
	}
	
/**
 * Проверка был ли телефон зарегистрирован в БД ранее
 * @param string $phone Номер телефона пользователя
 * @return boolean true - если такой телефон был найден, иначе - false
 */
	public function is_phone_exists($phone)
	{
		$this->buf = $this->get_user_row('phone', $phone);
		return $this->buf != null;
	}
	
/**
 * Проверка был ли телефон зарегистрирован в БД ранее
 * @param string $nick псевдоним пользователя
 * @return boolean true - если такой псевдоним был найден, иначе - false
 */
	public function is_nick_exists($nick)
	{
		$this->buf = $this->get_nick_row($nick);
		return $this->buf != null;
	}

/**
 * Получение записи из таблицы пользоватлей
 * @param int $user_id ID пользователя
 * @param string $phone телефон пользователя
 * @param string $email электронный адрес пользователя
 * @return array возращается строка в виде ассоциированного массива
 * а если нужный user_id не найден - null
 */
	public function get_user_row($param_name, $param_value)
	{
		if($param_name == null || $param_value == null)
		{
			trigger_error($this->loc->gets("dberr", "phone-exists"), E_USER_ERROR);
		}
		
		$sql = "SELECT ".$this->user_fields." FROM users WHERE %s = %s";
		$params = array($param_name, $this->db->escape($param_value));
		$sql = vsprintf($sql, $params);
		$this->show2($sql);
		
		$res = $this->db->query($sql);
		if($this->db->num_rows($res) === 0) return null;
		$row = $this->db->fetch_assoc($res);
		return $row;
	}
	

/**
 * Получение записи из таблицы логинов
 * @param string $login логин (псевдоним) пользователя
 * @return array возращается строка в виде ассоциированного массива
 * а если login не найден - null
 */
	public function get_nick_row($nick)
	{
		$sql = "SELECT ".$this->nick_fields." FROM nicks WHERE nick = '".$nick."'";
		$this->show2($sql);
		
		$res = $this->db->query($sql);
		if($this->db->num_rows($res) === 0) return null;
		$row = $this->db->fetch_assoc($res);
		return $row;
	}
	

/**
 * Создание новой учетной записи пользователя
 *
 * @return int Возвращается новое присвоенное user_id 
 */
	private function create_user_row($phone, $password, $email)
	{
		$sql = "INSERT INTO users(phone, password, email) VALUES(%s, %s, %s)";
		$params = array(
			$this->db->escape($phone),
			$this->db->escape(md5(md5($password).SALT)),
			$this->db->escape($email)
			);
		$sql = vsprintf($sql, $params);		
		$this->show2($sql);
		
		$this->db->query($sql);
		return $this->db->insert_id();
	}
	
/**
 * Создание записи для телефона пользователя
 *
s * @param int $user_id идентификатор пользователя
 * @param string $phone номер мобильного телефона
 * @return void
 */
	private function create_phone_row($user_id, $phone)
	{
		$sql = "INSERT INTO phones(phone, user_id) VALUES('$phone', $user_id)";
		$this->show2($sql);
		
		$this->db->query($sql);
	}
	

/**
 * Создание записи для логина/пароля пользователя
 *
 * @param int $user_id идентификатор пользователя
 * @param string $login псевдоним пользователя 
 * @param string $password пароль
 * @return void
 */
	private function create_nick_row($nick, $user_id)
	{
		$sql = "INSERT INTO nicks(nick, user_id) VALUES('$nick', $user_id)";
		$this->show2($sql);
		
		$this->db->query($sql);
	}
	

	private function get_url_sid()
	{
		$sid = $this->get_item($_REQUEST, 'sid', null);
		return $sid;
	}
	
	private function sid2uid($sid)
	{
		$sql = "SELECT user_id, dt_beg, dt_end FROM user_sessions WHERE session_id = '".$sid."'";
		$this->show2($sql);
		
		$res = $this->db->query($sql);
		if($this->db->num_rows($res) > 0)
		{
			$row = $this->db->fetch_assoc($res);
			return $row["user_id"];
		}
		else
			return null;
	}
	

	private function refresh_session()
	{
		if(empty($_SESSION['uid'])) return;
		if(empty($_SESSION['sid']))
		{
			$user_session = $this->last_sid_row();
			if(is_null($user_session))
			{
				$this->insert_session();
			}
			else
			{
				$_SESSION['sid'] = $user_session['session_id'];
				$this->update_session();
			}
		}
		else
			$this->update_session();
		$this->set_cookie_sid($_SESSION['sid']);
	}

	private function generate_sid()
	{
		$date = new DateTime();
		return md5(SALT.date('YmdHisu').$_SERVER['REMOTE_ADDR']);
	}
	
	private function last_sid_row()
	{
		if(empty($_SESSION['uid'])) return null;
		$sql = "SELECT session_id, user_id, dt_beg, dt_end FROM user_sessions WHERE user_id = '".$_SESSION['uid']."' ORDER BY dt_end DESC";
		$this->show2($sql);
		
		$res = $this->db->query($sql);
		if($this->db->num_rows($res) == 0) return null;
		return $this->db->fetch_assoc($res);
	}
	
	private function insert_session()
	{
		if(empty($_SESSION['uid']))
			trigger_error($this->loc->get("dberr", "ins-sid-4-null"), E_USER_ERROR);

		$_SESSION['sid'] = $this->generate_sid();
		$sql = "INSERT INTO user_sessions(session_id, user_id) VALUES('".$_SESSION['sid']."', ".$_SESSION['uid'].")";
		$this->show2($sql);
		
		$this->db->query($sql);
	}
	
	private function update_session()
	{
		if(empty($_SESSION['uid']))
			trigger_error($this->loc->get("dberr", "ins-sid-4-null"), E_USER_ERROR);
		if(empty($_SESSION['sid']))
			trigger_error($this->loc->get("dberr", "upd-null-sid"), E_USER_ERROR);

		// обновляем dt_end (время последнего визита), но сохраняем dt_beg (время первого)
		$sql = "UPDATE user_sessions SET dt_beg = dt_beg, dt_end = now() WHERE session_id = '".$_SESSION['sid']."'";
		$this->show2($sql);
		
		$this->db->query($sql);
		// если не было обновлено ни одной строки, то значит сессии нет в бд и ее надо создать
		if($this->db->affected_rows() == 0) $this->insert_session();
		
	}
	
	private function set_cookie_sid($sid)
	{
		setcookie("sid", $sid);
	}
	
	private function load_user_info()
	{
		
	}
	
	public function get_state()
	{
		return empty($_SESSION['uid']);
	}
	
	
	private function get_item($items, $key, $def = null)
	{
		if(is_null($items)) return $def;
		if(!is_array($items)) return $def;
		return isset($items[$key]) ? $items[$key] : $def;
	}
	
	private function show2($msg)
	{
		Logger::Write("SQL", $msg);
		echo $msg."\n";
	}
	
	private function log_sql($sql)
	{
		if(LOG_SQL) Logger::Write("UID SQL", $sql);
	}
    
	private function log2($msg)
	{
		Logger::Write("UID", $msg);
	}
    
	private function log2db($code, $msg)
	{
		$sql = "INSERT INTO user_log(u_agent, u_addr, code, info) VALUES(%s, %s, %s, %s)";
		$params = array(
			$this->db->escape($_SERVER['HTTP_USER_AGENT']),
			$this->db->escape($_SERVER['REMOTE_ADDR']),
			$this->db->escape($code),
			$this->db->escape($msg)
			);
		$sql = vsprintf($sql, $params);		
		$this->show2($sql);
		
		$this->db->query($sql);
		$log_id = $this->db->insert_id();
		
		$this->log2("UID", 'user_log id: '.$log_id.' -> '.$code.': '.$msg);
	}
    
    private $db = null;
    
}
